/*
 * PlayerImpl.h
 *
 *  Created on: 2011/05/28
 *      Author: naoki
 */

#ifndef INCLUDED_NTENGINE_SOUND_PLAYERIMPL_H
#define INCLUDED_NTENGINE_SOUND_PLAYERIMPL_H

#include "NTEngine/NTEngine.h"
#include "NTEngine/Sound/Player.h"
#include "NTEngine/Base/ReferenceCounter.h"

#include "Sound/src/WaveImpl.h"

#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>

namespace NTEngine {
namespace Sound {

class Player::Impl : public ReferenceCounter {
public:
	Impl( Wave::Impl* bank, SLEngineItf engine, SLObjectItf outputMix )
	: mBank( bank )
	, mObject( NULL )
	, mPlay( NULL )
	, mVolume( NULL )
	, mPlaybackRate( NULL )
	, mPlayBufferQueue( NULL )
	, mLoopCount( Player::LOOP_NONE ) {
		// 手動コピーなので参照カウントをあげる
		if ( mBank ) {
			mBank->refer();
		}
		// configure audio source
		SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {
			SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2
		};
		SLDataSource audioSrc = {
			&loc_bufq, &mBank->mFormat
		};
		// configure audio sink
		SLDataLocator_OutputMix loc_outmix = {
			SL_DATALOCATOR_OUTPUTMIX, outputMix
		};
		SLDataSink audioSnk = {
			&loc_outmix, NULL
		};

		SLresult result;
		// create audio player
		const SLuint32 numInterfaces = 3;
		const SLInterfaceID ids[ numInterfaces ] = {
			SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
			SL_IID_VOLUME,
			SL_IID_PLAYBACKRATE,
		};
		const SLboolean req[ numInterfaces ] = {
			SL_BOOLEAN_TRUE,
			SL_BOOLEAN_TRUE,
			SL_BOOLEAN_TRUE,
		};
		// create audio player
		result = ( *engine )->CreateAudioPlayer( engine
			, &mObject
			, &audioSrc, &audioSnk
			, numInterfaces, ids, req
		);
		ASSERT( SL_RESULT_SUCCESS == result );
		// realize the player
		result = ( *mObject )->Realize( mObject, SL_BOOLEAN_FALSE );
		ASSERT( SL_RESULT_SUCCESS == result );
		// get the play interface
		result = ( *mObject )->GetInterface( mObject, SL_IID_PLAY, &mPlay );
		ASSERT( SL_RESULT_SUCCESS == result );
		// get the volume interface
		result = ( *mObject )->GetInterface( mObject, SL_IID_VOLUME, &mVolume );
		ASSERT( SL_RESULT_SUCCESS == result );
		// get the play-back-rate interface
		result = ( *mObject )->GetInterface( mObject, SL_IID_PLAYBACKRATE, &mPlaybackRate );
		ASSERT( SL_RESULT_SUCCESS == result );
		// get the buffer queue interface
		result = ( *mObject )->GetInterface( mObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &mPlayBufferQueue );
		ASSERT( SL_RESULT_SUCCESS == result );
		// register callback on the buffer queue
	    result = ( *mPlayBufferQueue )->RegisterCallback( mPlayBufferQueue, bqCallback, this );
	    ASSERT( SL_RESULT_SUCCESS == result );

	    fillBuffer();
	}

	~Impl() {
		if ( mObject != NULL ) {
			( *mObject )->Destroy( mObject );
			mObject = NULL;
		}
		if ( mBank ) {
			// 手動コピーなので参照カウントを減らし、必要なら削除する
			if ( mBank->unref() <= 0 ) {
				delete mBank;
			}
			mBank = 0;
		}
	}

	void play() {
		SLresult result = ( *mPlay )->SetPlayState( mPlay, SL_PLAYSTATE_PLAYING );
		ASSERT( SL_RESULT_SUCCESS == result );
	}

	void pause() {
		SLresult result = ( *mPlay )->SetPlayState( mPlay, SL_PLAYSTATE_PAUSED );
		ASSERT( SL_RESULT_SUCCESS == result );
	}

	void stop() {
		SLresult result = ( *mPlay )->SetPlayState( mPlay, SL_PLAYSTATE_STOPPED );
		ASSERT( SL_RESULT_SUCCESS == result );
		// 次の再生のためにBQをリセットしておく
		result = ( *mPlayBufferQueue )->Clear( mPlayBufferQueue );
		ASSERT( SL_RESULT_SUCCESS == result );
		fillBuffer();
	}

	bool isPlaying() const {
		SLuint32 state;
		SLresult result = ( *mPlay )->GetPlayState( mPlay, &state );
		ASSERT( SL_RESULT_SUCCESS == result );
		return ( SL_PLAYSTATE_PLAYING == state );
	}

	void setVolume( int v ) {
		ASSERT( 0 <= v && v <= 100 );
		int tmp = v * 0xffff;
		tmp /= 100;
		tmp -= 0x8000;
		SLmillibel millibel = static_cast< SLmillibel >( tmp );
		SLresult result = ( *mVolume )->SetVolumeLevel( mVolume, millibel );
		ASSERT( SL_RESULT_SUCCESS == result );
	}

	int volume() const {
		SLmillibel millibel = 0;
		SLresult result = ( *mVolume )->GetVolumeLevel( mVolume, &millibel );
		int ret = static_cast< int >( millibel );
		ret += 0x8000; // 0 ~ 0xffffに調整
		ret *= 100; // 最初に100倍
		ret /= 0xffff; // 0xffffで割る
		return ret;
	}

	void setPitch( float v ) {
		// 基本は0.5倍から2倍
		ASSERT( 0.5f <= v && v <= 2.f );
		// ハードウェアによっては最小、最大が異なる場合があるので調整する
		SLpermille minPermille;
		SLpermille maxPermille;
		SLresult result = ( *mPlaybackRate )->GetRateRange( mPlaybackRate, 0, &minPermille, &maxPermille, NULL, NULL );
		ASSERT( SL_RESULT_SUCCESS == result );
		SLpermille permille = static_cast< SLpermille >( 1000.f * v );
		if ( permille < minPermille ) {
			permille = minPermille;
		} else if ( maxPermille < permille ) {
			permille = maxPermille;
		}
		// 補正した値を設定する
		result = ( *mPlaybackRate )->SetRate( mPlaybackRate, permille );
		ASSERT( SL_RESULT_SUCCESS == result );
	}

	float pitch() const {
		SLpermille permille;
		SLresult result = ( *mPlaybackRate )->GetRate( mPlaybackRate, &permille );
		ASSERT( SL_RESULT_SUCCESS == result );
		return static_cast< float >( permille ) / 1000.f;
	}

	static void bqCallback( SLAndroidSimpleBufferQueueItf bq, void* context ) {
		ASSERT( context );
		Impl* self = static_cast< Impl* >( context );
		ASSERT( self->mPlayBufferQueue == bq );
		self->onCallback();
	}

	void onCallback() {
		if ( mLoopCount != Player::LOOP_NONE ) {
			if ( mLoopCount > Player::LOOP_NONE ) {
				--mLoopCount;
			}
			fillBuffer();
		}
	}

	void fillBuffer() {
		if ( mBank ) {
			SLresult result = ( *mPlayBufferQueue )->Enqueue( mPlayBufferQueue, mBank->mBuffer, mBank->mSize );
			ASSERT( SL_RESULT_SUCCESS == result );
		}
	}

	// バッファ
	Wave::Impl* mBank;

	// SLインターフェイス
	SLObjectItf mObject;
	SLPlayItf mPlay;
	SLVolumeItf mVolume;
	SLPlaybackRateItf mPlaybackRate;
	SLAndroidSimpleBufferQueueItf mPlayBufferQueue;
	int mLoopCount;
};

} // namespace Sound
} // namespace NTEngine

#endif // INCLUDED_NTENGINE_SOUND_PLAYERIMPL_H
